﻿using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using AutoTAU.Properties;
using NativeWifi;

namespace AutoTAU
{
    /// <summary>
    /// Wifi Exception
    /// </summary>
    public class WifiException : Exception
    {
        public WifiException()
        {
        }

        public WifiException(string message) : base(message)
        {
        }

        public WifiException(string message, Exception inner) : base(message, inner)
        {
        }
    }

    /// <summary>
    /// Wifi Interface Registration Exception
    /// </summary>
    public class WifiRegisterException : WifiException
    {
        public WifiRegisterException()
        {
        }

        public WifiRegisterException(string message) : base(message)
        {
        }

        public WifiRegisterException(string message, Exception inner) : base(message, inner)
        {
        }
    }

    /// <summary>
    /// Manage automatic wifi authentications to the PublicTAU network
    /// </summary>
    internal static class WifiAuthManager
    {
        #region Delegates

        /// <summary>
        /// Wifi Authentication Event Handler Delegate
        /// </summary>
        public delegate void WifiAuthEventHandler(WifiStatus status, string title, string msg, bool critical);

        #endregion

        /// <summary>
        /// NativeWifi Wlan Client
        /// </summary>
        private static WlanClient wlanClient;

        /// <summary>
        /// Wifi Authentication Event
        /// </summary>
        public static event WifiAuthEventHandler WifiAuthEvent;

        /// <summary>
        /// Register notification callback for detecting that a new wireless connection has been made
        /// </summary>
        public static void Register()
        {
            try
            {
                wlanClient = new WlanClient();
            }
            catch (Win32Exception)
            {
                // An error occured, most likely due to the fact that WLAN AutoConfig has been replaced
                // with some proprietary, vendor-specific Wifi management utility.
                wlanClient = null;
            }

            if (wlanClient == null)
            {
                throw new WifiRegisterException("WLAN client is not initialized. AutoConfig error?");
            }

            try
            {
                // Register our event handler for each wifi interface
                foreach (WlanClient.WlanInterface Interface in wlanClient.Interfaces)
                {
                    try
                    {
                        Interface.WlanConnectionNotification += NewWlanConnectionHandler;
                    }
                    catch
                    {
                    }
                }
            }
            catch (Wlan.WlanException)
            {
            }
        }

        /// <summary>
        /// Authenticate now if we're connected to Public-TAU
        /// </summary>
        public static void AuthenticateIfConnected()
        {
            try
            {
                // Look for Public-TAU on each wifi interface
                if (IsPublicTAUConnected())
                {
                    Authenticate();
                }
                else
                {
                    WifiAuthEvent(WifiStatus.None, null, null, false);
                }
            }
            catch (Wlan.WlanException e)
            {
                throw new WifiRegisterException(
                    "Exception while authenticating if Public-TAU is available", e);
            }
        }

        /// <summary>
        /// Check if any of the interfaces is connected to Public-TAU
        /// </summary>
        /// <returns>Is an interface is connected to Public-TAU</returns>
        public static bool IsPublicTAUConnected()
        {
            if (wlanClient == null)
            {
                // Cannot determine if PublicTAU is connected
                return false;
            }

            // Look for Public-TAU for each wifi interface
            foreach (WlanClient.WlanInterface Interface in wlanClient.Interfaces)
            {
                try
                {
                    if (Interface.CurrentConnection.profileName.Contains("Public-TAU"))
                    {
                        return true;
                    }
                }
                
                    // Mask all exceptions
                catch
                {
                }
            }

            return false;
        }

        /// <summary>
        /// New connection event handler
        /// </summary>
        /// <param name="notifyData">Wlan notification data</param>
        /// <param name="connNotifyData">Wlan connection notification data</param>
        private static void NewWlanConnectionHandler(Wlan.WlanNotificationData notifyData,
                                                     Wlan.WlanConnectionNotificationData connNotifyData)
        {
            if ((((Wlan.WlanNotificationCodeAcm) notifyData.notificationCode ==
                  Wlan.WlanNotificationCodeAcm.ConnectionComplete)
                 || ((Wlan.WlanNotificationCodeAcm) notifyData.notificationCode ==
                     Wlan.WlanNotificationCodeAcm.PowerSettingChange))
                && connNotifyData.profileName.Contains("Public-TAU"))
            {
                // We're connected to Public-TAU, authenticate asynchronously
                var AuthThread = new Thread(AuthenticateIfConnected);
                AuthThread.Start();
            }

            else if (((Wlan.WlanNotificationCodeAcm) notifyData.notificationCode ==
                      Wlan.WlanNotificationCodeAcm.Disconnected)
                     && connNotifyData.profileName.Contains("Public-TAU"))
            {
                // We're disconnected from Public-TAU, reset state
                WifiAuthEvent(WifiStatus.None, null, null, false);
            }

            else if (((Wlan.WlanNotificationCodeAcm) notifyData.notificationCode ==
                      Wlan.WlanNotificationCodeAcm.ConnectionComplete)
                     && !connNotifyData.profileName.Contains("Public-TAU"))
            {
                // We're connected to a different network
                WifiAuthEvent(WifiStatus.ConnectedToAnotherNetwork, null, null, false);
            }
        }

        /// <summary>
        /// Authenticate with the PublicTAU authentication gateway
        /// </summary>
        public static void Authenticate()
        {
            try
            {
                WifiAuthEvent(WifiStatus.Authenticating, null, null, false);

                // Try to login to authentication server
                // FIXME: bad design, Login() should also handle WebException instead of
                // allowing it to propagate to the caller. I'll refactor this code someday.
                LoginStatus status = Login();
                switch (status)
                {
                    case LoginStatus.Success:
                        WifiAuthEvent(WifiStatus.Success, null, null, false);
                        break;
                    case LoginStatus.BadUsernameOrPassword:
                        // We want the user to see this error even if he disabled error balloons
                        WifiAuthEvent(WifiStatus.Failure, "שגיאה", "שם משתמש או סיסמא שגויים", true);
                        break;
                    case LoginStatus.UnknownServerResponse:
                        // We want the user to see this error even if he disabled error balloons
                        WifiAuthEvent(WifiStatus.Failure, "שגיאה", "תשובה לא מוכרת מהשרת", true);
                        break;
                    case LoginStatus.Unreachable:
                        WifiAuthEvent(WifiStatus.None, null, null, false);
                        break;
                }
            }
            catch (Exception ex)
            {
                // Could not reach authentication server
                if (IsPublicTAUConnected())
                {
                    // We're connected to Public-TAU and still couldn't reach the server.
                    // Perhaps we're online anyway? (in which case we don't care about the failure)
                    if (CheckIfOnline())
                    {
                        WifiAuthEvent(WifiStatus.Success, null, null, false);
                    }

                    else
                    {
                        // We're connected to Public-TAU but couldn't get online, report error
                        WifiAuthEvent(WifiStatus.Failure, "שגיאה", ex.Message, false);
                    }
                }

                else
                {
                    // We've failed because we've been disconnected from Public-TAU, reset state
                    WifiAuthEvent(WifiStatus.None, null, null, false);
                }
            }
        }

        /// <summary>
        /// Try to login
        /// </summary>
        /// <returns>Login status</returns>
        /// <exception cref="System.IO.IOException">Could not send POST</exception>
        /// <exception cref="System.Net.WebException">HTTP error</exception>
        /// <exception cref="System.Net.Sockets.SocketException">Could not resolve the auth server's address</exception>
        private static LoginStatus Login()
        {
            int attempts = 1;

            for (; attempts <= 10; attempts++)
            {
                try
                {
                    // Check if vwism.tau.ac.il can be resolved by DNS.
                    // This is a trick to avoid needless NBNS queries which always fail
                    // and introduce a huge timeout.
                    if (Dns.GetHostEntry("vwism.tau.ac.il").AddressList.Length == 0)
                    {
                        // Sleep and try again
                        System.Threading.Thread.Sleep(1000 * attempts);
                        continue;
                    }
            
                    // Post the authentication request
                    var postRequest = (HttpWebRequest) WebRequest.Create("https://vwism.tau.ac.il/login.html");
                    postRequest.KeepAlive = false;
                    postRequest.ContentType = "application/x-www-form-urlencoded";
                    postRequest.Method = "POST";
                    byte[] postBytes =
                        Encoding.ASCII.GetBytes(
                            String.Format(
                                "buttonClicked=4&err_flag=0&err_msg=&info_flag=0&info_msg=&redirect_url=&username={0}&password={1}",
                                Settings.Default.Username, Settings.Default.Password));
                    postRequest.ContentLength = postBytes.Length;
                    Stream postStream = postRequest.GetRequestStream();

                    postStream.WriteTimeout = 1000*attempts;
                    postStream.Write(postBytes, 0, postBytes.Length);

                    // Get the server's response
                    WebResponse postResponse = postRequest.GetResponse();
                    var postResponseStream = new StreamReader(postResponse.GetResponseStream());
                    string postResponseData = postResponseStream.ReadToEnd();

                    // Test the server's response
                    if (postResponseData.Contains("Login Successful") ||
                        postResponseData.Contains("You are already logged in. No further action is required on your part."))
                    {
                        // Login successful
                        return LoginStatus.Success;
                    }

                    if (postResponseData.Contains(
                        "<INPUT TYPE=\"hidden\" NAME=\"err_flag\" SIZE=\"16\" MAXLENGTH=\"15\" VALUE=\"1\">"))
                    {
                        // Bad username or password
                        return LoginStatus.BadUsernameOrPassword;
                    }

                    // Unknown server response
                    return LoginStatus.UnknownServerResponse;
                }

                catch
                {
                    // Try again
                }
            }

            return LoginStatus.Unreachable;
        }

        /// <summary>
        /// Checks if we're online by pinging a primary DNS server by anycast IP (4.2.2.4)
        /// </summary>
        /// <returns>Whether online or not</returns>
        public static bool CheckIfOnline()
        {
            var ping = new Ping();

            // Timeout after 1 second
            PingReply pingReply = ping.Send("4.2.2.4", 1000);

            if (pingReply != null)
            {
                return (pingReply.Status == IPStatus.Success);
            }

            return false;
        }
    }
}